package com.example.mykotlin

import com.example.mykotlin.base.flex.MyInsertUpdateListener
import com.example.mykotlin.entity.TableInfo
import com.example.mykotlin.mapper.CodeGenerateMapper
import java.io.File
import org.apache.commons.lang3.StringUtils
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest


@SpringBootTest
class CodeGenerate {

    @Autowired
    lateinit var codeGenerateMapper: CodeGenerateMapper


    @Test
    fun codeGenerate() {
        arrayOf("test").forEach {
            codeGenerateAll(it, true)
        }
    }

    // 根据表名生成所有代码，包括entity、mapper、controller
    // 参数: tableName - 表名，isCover - 是否覆盖已有代码（默认false）
    fun codeGenerateAll(tableName: String, isCover: Boolean = false) {
        try {
            // 生成entity类，包括表名、表注释、表信息
            val (entityName, tableCommentInfo, tableInfos) = generateEntity(tableName, isCover)
            // 根据entity生成mapper接口和xml文件
            generateMapper(entityName.toString(), isCover)
            // // 生成service
            // generateService(entityName.toString(), isCover)
            // 根据entity、表注释、表信息生成controller类，包括CRUD操作
            generateController(
                entityName.toString(),
                tableCommentInfo.toString(),
                tableInfos as List<TableInfo>,
                isCover
            )
        } catch (e: Exception) {
            // 捕获异常并打印错误信息
            e.printStackTrace()
        }
    }


    companion object {
        /**
         * 生成的kotlin类文件所放置的目录
         */
        val projectBasePath =
            Companion::class.java.getResource("")!!.path.replace("/target/test-classes/.*".toRegex(), "")
        val basePath = projectBasePath + "\\src\\main\\kotlin\\com\\example\\mykotlin\\"
        val baseXmlPath = projectBasePath + "\\src\\main\\resources\\mapperxml\\"
        val basePackageName = "com.example.mykotlin"
        val controllerPath = "controller"
        val servicePath = "service"
        val mapperPath = "mapper"
        val modelPath = "model"
        val entityPath = "entity"
        val classEnd = ".kt"
    }

    fun underlineToHump(name: String, isAllHump: Boolean = false): String {
        val split = name.split("_")
        var humpName: String =
            if (isAllHump) split[0].lowercase().replaceFirstChar { it.uppercaseChar() } else split[0].lowercase()
        if (split.size > 1) {
            for (i in 1 until split.size) {
                humpName += split[i].lowercase().replaceFirstChar { it.uppercaseChar() }
            }
        }
        return humpName
    }


    fun generateEntity(tableName: String, isCover: Boolean = false): Array<Any> {
        val tn = tableName.lowercase()
        val entityClassName = underlineToHump(tn, true)

        val tableCommentInfos = codeGenerateMapper.getTableCommentInfo(tn)
        val stringBuilder = StringBuilder()
        for (s in tableCommentInfos) {
            stringBuilder.append(s)
            if ('表' == s) {
                break
            }
        }
        val tableCommentInfo = stringBuilder.toString()

        val fieldInfos = ArrayList<String>()
        val tableInfos = codeGenerateMapper.getTableInfo(tn)
        tableInfos.forEach {
            val fieldType = when (it.dataType?.uppercase()) {
                "TINYINT" -> "Int"
                "SMALLINT" -> "Int"
                "MEDIUMINT" -> "Int"
                "INT" -> "Int"
                "INTEGER" -> "Int"
                "BIGINT" -> "Long"

                "FLOAT" -> "Double"
                "DOUBLE" -> "Double"
                "DECIMAL" -> "BigDecimal"

                "DATE" -> "LocalDate"
                "YEAR" -> "LocalDate"
                "TIME" -> "LocalDateTime"
                "DATETIME" -> "LocalDateTime"
                "TIMESTAMP" -> "LocalDateTime"

                else -> "String"
            }
            val fieldName = underlineToHump(it.columnName!!)
            val s = "    var ${fieldName}: $fieldType? = null"

            val idAnnotation = "    @Id\n"
            val nullableStringInsert = "    @field:NotEmpty(message = \"不能为空\", groups = [Insert::class])\n"
            val nullableInsert = "    @field:NotNull(message = \"不能为空\", groups = [Insert::class])\n"

            val idMinUpdate = "    @field:Min(value = 1, message = \"主键不能小于1\", groups = [Update::class])\n"
            val idNullUpdate = "    @field:NotNull(message = \"不能为空\", groups = [Update::class])\n"
            val nullableStringUpdate = "    @field:NotEmpty(message = \"不能为空\", groups = [Update::class])\n"

            val fieldAnnotationInfo = if ("PRI" == it.columnKey) {
                val fieldInfo =
                    if (fieldType == "Int" || fieldType == "Long") idMinUpdate + idNullUpdate else if (fieldType == "String") nullableStringUpdate else idNullUpdate
                idAnnotation + fieldInfo
            } else {
                if ("NO" == it.isNullable && MyInsertUpdateListener.CREATE_TIME != fieldName && MyInsertUpdateListener.UPDATE_TIME != fieldName) {
                    if (fieldType == "String") nullableStringInsert else nullableInsert
                } else ""
            }
            val dateJsonFormat = when (fieldType) {
                "LocalDateTime" -> "    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n"
                "LocalDate" -> "    @JsonFormat(pattern = \"yyyy-MM-dd\", timezone = \"GMT+8\")\n"
                else -> ""
            }
            val openApiInfo =
                if (StringUtils.isBlank(it.columnComment)) "" else "    @Schema(title = \"${it.columnComment}\")\n"
            val fieldLengthFormat =
                if (it.dataLength != null && it.dataLength!! > 0 && fieldType == "String") "    @field:Size(max = ${it.dataLength}, message = \"长度不能超过${it.dataLength}\")\n" else ""
            fieldInfos.add(fieldAnnotationInfo + dateJsonFormat + fieldLengthFormat + openApiInfo + s)
            it.fieldName = fieldName
            it.fieldType = fieldType
        }
        val allInfos = arrayOf(entityClassName, tableCommentInfo, tableInfos)
        val file =
            File(basePath + modelPath + File.separator + entityPath + File.separator + entityClassName + classEnd)
        if (file.exists() && !isCover) return allInfos

        val readTmp = """package ${basePackageName}.${modelPath}.${entityPath}

import com.example.mykotlin.base.valid.Insert
import com.example.mykotlin.base.valid.Update
import com.fasterxml.jackson.annotation.JsonFormat
import com.mybatisflex.annotation.Id
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import java.time.LocalDateTime

@Schema(name = "$entityClassName", title = "$tableCommentInfo")
open class ${entityClassName}(
${fieldInfos.joinToString(",\n\n")}
)"""
        file.writeText(readTmp)
        return allInfos
    }

    fun generateMapper(entityName: String, isCover: Boolean = false) {
        val file =
            File(basePath + mapperPath + File.separator + entityName + mapperPath.replaceFirstChar { it.uppercaseChar() } + classEnd)
        if (file.exists() && !isCover) return
        val readTmp = """package ${basePackageName}.${mapperPath}

import ${basePackageName}.${modelPath}.${entityPath}.${entityName}
import com.mybatisflex.core.BaseMapper

interface ${entityName}Mapper : BaseMapper<${entityName}>"""
        file.writeText(readTmp)

        val xmlTmp = """<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="${basePackageName}.${mapperPath}.${entityName}Mapper">
    <!--
        <select id="" resultType="${basePackageName}.${modelPath}.${entityPath}.${entityName}">
        </select>
    -->
    <!--
        <insert id=""></insert>
        <delete id=""></delete>
        <update id=""></update>
    -->
</mapper>"""
        val xmlFile = File(baseXmlPath + entityName + mapperPath.replaceFirstChar { it.uppercaseChar() } + ".xml")
        if (xmlFile.exists() && !isCover) return
        xmlFile.writeText(xmlTmp)
    }

    fun generateService(entityName: String, isCover: Boolean = false) {
        val file =
            File(basePath + servicePath + File.separator + entityName + servicePath.replaceFirstChar { it.uppercaseChar() } + classEnd)
        if (file.exists() && !isCover) return
        val readTmp = """package ${basePackageName}.${servicePath}

import ${basePackageName}.${mapperPath}.${entityName}Mapper
import ${basePackageName}.${modelPath}.${entityPath}.${entityName}
import com.mybatisflex.spring.service.impl.ServiceImpl
import org.springframework.stereotype.Service

@Service
class ${entityName}Service : ServiceImpl<${entityName}Mapper, ${entityName}>()"""
        file.writeText(readTmp)
    }

    fun generateController(
        entityName: String,
        tableCommentInfo: String,
        tableInfos: List<TableInfo>,
        isCover: Boolean = false
    ) {
        val entityVariableName = entityName.replaceFirstChar { it.lowercase() }
        var idFieldName = ""
        var idFieldType = ""
        val wrappersTmp = StringBuilder()
        val copyTmp = StringBuilder()
        for (t in tableInfos) {
            if ("PRI" == t.columnKey) {
                idFieldName = t.fieldName ?: ""
                idFieldType = t.fieldType ?: ""
            }
            if (t.fieldType?.contains("Date") == false) wrappersTmp.append("                (${entityName}::${t.fieldName} eq ${entityVariableName}.${t.fieldName}).`when`(${if (t.fieldType == "String") "StringUtils.isNotEmpty(${entityVariableName}.${t.fieldName})" else "${entityVariableName}.${t.fieldName} != null"}),\n")
            copyTmp.append("        if (${if (t.fieldType == "String") "StringUtils.isNotEmpty(${entityVariableName}.${t.fieldName})" else "${entityVariableName}.${t.fieldName} != null"}) byId.${t.fieldName} = ${entityVariableName}.${t.fieldName}\n")
        }

        val tableCommentBuilder = StringBuilder()
        for (s in tableCommentInfo) {
            if ('表' == s) break
            tableCommentBuilder.append(s)
        }
        val simpleTableComment = tableCommentBuilder.toString()

        val tmp = """package ${basePackageName}.${controllerPath}

import cn.dev33.satoken.annotation.SaCheckPermission
import com.example.mykotlin.base.result.PageParam
import com.example.mykotlin.base.result.ServiceException
import com.example.mykotlin.base.util.runService
import com.example.mykotlin.base.util.selectById
import com.example.mykotlin.base.util.updateById
import com.example.mykotlin.base.valid.Insert
import com.example.mykotlin.base.valid.Update
import com.example.mykotlin.model.entity.Test
import com.mybatisflex.kotlin.extensions.db.deleteById
import com.mybatisflex.kotlin.extensions.db.insert
import com.mybatisflex.kotlin.extensions.db.paginate
import com.mybatisflex.kotlin.extensions.kproperty.eq
import com.mybatisflex.kotlin.extensions.wrapper.andAll
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.constraints.${if (idFieldType == "Int" || idFieldType == "Long") "Min" else if (idFieldType == "String") "NotEmpty" else "NotNull"}
import org.apache.commons.lang3.StringUtils
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "${tableCommentInfo}Controller", description = "相关接口")
@RestController
@Validated
@RequestMapping("/${entityVariableName}")
class ${entityName}Controller {

    @GetMapping
    @Operation(summary = "根据主键查询${simpleTableComment}", description = "返回${simpleTableComment}${entityName}对象")
    @SaCheckPermission("GET_${entityVariableName}")
    fun get${entityName}ById(@RequestParam @Parameter(description = "${tableCommentInfo}主键") ${if (idFieldType == "Int" || idFieldType == "Long") "@Min(value = 1, message = \"主键不能小于1\")" else if (idFieldType == "String") "@NotEmpty(message = \"主键不能为空\")" else "@NotNull(message = \"主键不能为空\")"} id: ${idFieldType}) =
        runService { selectById<${entityName}>(id) }

    @GetMapping("/inPage")
    @Operation(summary = "分页条件查询${simpleTableComment}", description = "返回${simpleTableComment}${entityName}对象列表")
    @SaCheckPermission("GET_${entityVariableName}_inPage")
    fun get${entityName}InPage(${entityVariableName}: ${entityName}, @Validated p: PageParam) = runService {
        paginate<${entityName}>(p.page()) {
            andAll(
$wrappersTmp            )
        }
    }

    @PostMapping
    @Operation(summary = "新增${simpleTableComment}", description = "返回新增的${simpleTableComment}${entityName}对象")
    @SaCheckPermission("POST_${entityVariableName}")
    fun insert${entityName}(@RequestBody @Validated(Insert::class) ${entityVariableName}: ${entityName}) = runService {
        ${entityVariableName}.${idFieldName} = null
        insert($entityVariableName)
        $entityVariableName
    }

    @PutMapping
    @Operation(summary = "根据主键修改${simpleTableComment}", description = "返回修改后的${simpleTableComment}${entityName}对象")
    @SaCheckPermission("PUT_${entityVariableName}")
    fun update${entityName}(@RequestBody @Validated(Update::class) ${entityVariableName}: ${entityName}) = runService {
        val byId = selectById<${entityName}>(${entityVariableName}.${idFieldName}!!) ?: throw ServiceException("[${idFieldName}=${"$"}{${entityVariableName}.${idFieldName}}]数据不存在")
        updateById(${entityVariableName})
${copyTmp}        byId
    }

    @DeleteMapping
    @Operation(summary = "根据主键删除${simpleTableComment}", description = "返回被删除的${simpleTableComment}${entityName}对象")
    @SaCheckPermission("DELETE_${entityVariableName}")
    fun delete${entityName}ById(@RequestParam @Parameter(description = "${tableCommentInfo}主键") ${if (idFieldType == "Int" || idFieldType == "Long") "@Min(value = 1, message = \"主键不能小于1\")" else if (idFieldType == "String") "@NotEmpty(message = \"主键不能为空\")" else "@NotNull(message = \"主键不能为空\")"} id: ${idFieldType}) =
        runService {
            val byId = selectById<${entityName}>(id) ?: throw ServiceException("[id=${"$"}{id}]数据不存在")
            if (deleteById<${entityName}>(id) > 0) byId else throw ServiceException("[id=${"$"}{id}]数据删除失败")
        }

}"""
        val file =
            File(basePath + controllerPath + File.separator + entityName + controllerPath.replaceFirstChar { it.uppercaseChar() } + classEnd)
        if (file.exists() && !isCover) return
        file.writeText(tmp)
    }

}